package cas.cs4tb3.mellowd.parser;
import org.antlr.v4.runtime.Token;
import java.util.HashMap;
import java.util.Map;A symbol table holds references. It is simply a Map at its core but it provides type
and exception handling functionality. The @SuppressWarnings("unchecked") stops the compiler
from incorrectly warning about unchecked casts as the check is done via reflection and the
compiler isn’t convinced that it has been checked.
@SuppressWarnings("unchecked")
public class SymbolTable {The declarations are the actually name -> value mappings.
private Map<String, Object> declarations;
public SymbolTable() {
declarations = new HashMap<>();
}addDeclaration is the only data input method for this class. It adds a
new mapping for the identifier to the value. It will overwrite an existing
data and will return true if it does so.
public boolean addDeclaration(String identifier, Object value) {
return this.declarations.put(identifier, value) != null;
}getDeclarationValue is the based data output method for this class. It takes
the name of the identifier to lookup and the expected type. If the value
does not exist this method simply returns null. If the value exists but is the wrong type
this method will treat the identifier as non-existent and return null. Otherwise the
value is returned.
public <T> T getDeclarationValue(String identifier, Class<T> type) {
Object value = declarations.get(identifier);
return type.isInstance(value) ? (T) value : null;
}getDeclarationValueOrThrow is similar in function to getDeclarationValue but instead
of returning null, the appropriate exception will be thrown.
public <T> T getDeclarationValueOrThrow(Token token, Class<T> type) {
Object value = declarations.get(token.getText());If the value is null then there is nothing defined so throw an UndefinedReferenceException.
if (value == null)
throw new UndefinedReferenceException(token, "Identifier ("+token.getText()+") is undefined.");If the type of the value is incorrect then something is defined but it is the wrong type so throw an IncorrectTypeException.
if (!type.isInstance(value))
throw new IncorrectTypeException(token, "Identifier ("+token.getText()+") points to a "+value.getClass().getSimpleName()+" not a "+type.getSimpleName());Otherwise all is fine so we can safely cast and return the value
return (T) value;
}getType is a utility method that will return the type of the data. This
will return null if the identifier is not defined.
public Class<?> getType(String identifier) {
Object value = declarations.get(identifier);
return value == null ? null : value.getClass();
}identifierTypeIs preforms a type check on the identifier. It
will return true if the identifier is defined and points to an object
that can safely be cast to type
public boolean identifierTypeIs(String identifier, Class<?> type) {
Object value = declarations.get(identifier);
return value != null && type.isAssignableFrom(value.getClass());
}checkType is the equivalent of identifierTypeIs throwing an exception
if the type is defined and incorrect (not able to be cast to type).
public void checkType(Token token, Class<?> type) {
Object value = declarations.get(token.getText());
if (value != null && !type.isAssignableFrom(value.getClass()))
throw new IncorrectTypeException(token, "Identifier (" + token.getText() + ") points to a " + value.getClass().getSimpleName() + " not a " + type.getSimpleName());
}checkExists is a utility method that throws an exception if the identifier that
the token points to is not defined.
public void checkExists(Token token) {
Object value = declarations.get(token.getText());
if (value == null)
throw new UndefinedReferenceException(token, "Identifier ("+token.getText()+") is undefined.");
}
}